/*
 * JBox2D - A Java Port of Erin Catto's Box2D
 *
 * JBox2D homepage: http://jbox2d.sourceforge.net/
 * Box2D homepage: http://www.box2d.org
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package org.jbox2d.common;

import net.worlds.WorldsException;
import net.worlds.Main;
import net.worlds.MainView;
import net.worlds.Util;

/**
 * A 2-dimensional vector class.  Used heavily in JBox2d.
 * djm: added ToOut methods
 */
public class Vec2D {
    public static final Vec2D ORIGIN = new Vec2D(0, 0);
	/** Should we count Vec2 creations? */
	static public boolean watchCreations = true;
	/**
	 * Running count of Vec2 creations.  Must be zeroed out
	 * manually (perhaps at start of time step).  Incremented
	 * in Vec2 constructor if watchCreations flag is true.
	 * <BR><BR>
	 * Mainly used for optimization purposes, since temporary
	 * Vec2 creation is often a bottleneck.
	 */
	static public int creationCount = 0;

	public float x, y;

        public static Vec2D fromString(String s) throws WorldsException
        {
            try
            {
                String[] parts = s.split(",");
                return new Vec2D(Float.parseFloat(parts[0]), Float.parseFloat(parts[1]));
            }
            catch(Exception ex)
            {
                throw new WorldsException("Error parsing Vec2D value: "+s);
            }
        }

	public Vec2D() {
		this(0, 0);
	}
        public Vec2D(String s)
        {
            //should already be validated
            String[] parts = s.split(",");
            x = Float.parseFloat(parts[0]);
            y = Float.parseFloat(parts[1]);
        }
        public Vec2D(double a)
        {
            x = (float) Math.cos(a);
            y = (float) Math.sin(a);
        }

	public Vec2D(float x, float y) {
		if (Vec2D.watchCreations) {
			++Vec2D.creationCount;
		}
		this.x = x;
		this.y = y;
		// testbed.PTest.debugCount++;
	}

	public Vec2D( Vec2D toCopy) {
		this(toCopy.x, toCopy.y);
	}

	/** Zero out this vector. */
	public final void setZero() {
		x = 0.0f;
		y = 0.0f;
	}

	/** Set the vector component-wise. */
	public final void set(float x, float y) {
		this.x = x;
		this.y = y;
	}

	/** Set this vector to another vector. */
	public final Vec2D set(Vec2D v) {
		this.x = v.x;
		this.y = v.y;
		return this;
	}

	/** Return the sum of this vector and another; does not alter either one. */
	public final Vec2D add(Vec2D v) {
		return new Vec2D(x + v.x, y + v.y);
	}



	/** Return the difference of this vector and another; does not alter either one. */
	public final Vec2D sub(Vec2D v) {
		return new Vec2D(x - v.x, y - v.y);
	}

	/** Return this vector multiplied by a scalar; does not alter this vector. */
	public final Vec2D mul(float a) {
		return new Vec2D(x * a, y * a);
	}
        
	public final Vec2D mul(Vec2D v) {
		return new Vec2D(x * v.x, y * v.y);
	}
        
        public final Vec2D div(Vec2D v) {
		return new Vec2D(x / v.x, y / v.y);
	}
        
	public final Vec2D div(float a) {
		return new Vec2D(x / a, y / a);
	}

	/** Return the negation of this vector; does not alter this vector. */
	public final Vec2D negate() {
		return new Vec2D(-x, -y);
	}
        public float toAngle()
        {
            return MathUtils.atan2(y, x);
        }

	/** Flip the vector and return it - alters this vector. */
	public final Vec2D negateLocal() {
		x = -x;
		y = -y;
		return this;
	}

	/** Add another vector to this one and returns result - alters this vector. */
	public final Vec2D addLocal(Vec2D v) {
		x += v.x;
		y += v.y;
		return this;
	}

	/** Adds values to this vector and returns result - alters this vector. */
	public final Vec2D addLocal( float x, float y) {
		this.x+=x;
		this.y+=y;
		return this;
	}

	/** Subtract another vector from this one and return result - alters this vector. */
	public final Vec2D subLocal(Vec2D v) {
		x -= v.x;
		y -= v.y;
		return this;
	}

	/** Multiply this vector by a number and return result - alters this vector. */
	public final Vec2D mulLocal(float a) {
		x *= a;
		y *= a;
		return this;
	}

	/** Return the length of this vector. */
	public final float length() {
		return MathUtils.sqrt(x * x + y * y);
	}

	/** Return the squared length of this vector. */
	public final float lengthSquared() {
		return (x*x + y*y);
	}

	/** Normalize this vector and return the length before normalization.  Alters this vector. */
	public final Vec2D normalized() {
		float length = length();
		if (length < Settings.EPSILON) {
			return new Vec2D(0, 0);
		}

		float invLength = 1.0f / length;
		return new Vec2D(x * invLength, y * invLength);
	}
        public final float normalize() {
		float length = length();
		if (length < Settings.EPSILON) {
			return 0f;
		}

		float invLength = 1.0f / length;
		x *= invLength;
		y *= invLength;
		return length;
	}

	/** True if the vector represents a pair of valid, non-infinite floating point numbers. */
	public final boolean isValid() {
		return x != Float.NaN && x != Float.NEGATIVE_INFINITY
		&& x != Float.POSITIVE_INFINITY && y != Float.NaN
		&& y != Float.NEGATIVE_INFINITY && y != Float.POSITIVE_INFINITY;
	}

	/** Return a new vector that has positive components. */
	public final Vec2D abs() {
		return new Vec2D(MathUtils.abs(x), MathUtils.abs(y));
	}

	/* djm created */
	public final void absLocal(){
		x = MathUtils.abs(x);
		y = MathUtils.abs(y);
	}

	@Override
	/** Return a copy of this vector. */
	public final Vec2D clone() {
		return new Vec2D(x, y);
	}

        @Override
        public String toString()
        {
            return Util.roundTwoDecimals(x)+","+Util.roundTwoDecimals(y);
        }

	/*
	 * Static
	 */

	public final static Vec2D abs(Vec2D a) {
		return new Vec2D(MathUtils.abs(a.x), MathUtils.abs(a.y));
	}

	/* djm created */
	public final static void absToOut(Vec2D a, Vec2D out){
		out.x = MathUtils.abs( a.x);
		out.y = MathUtils.abs( a.y);
	}

	public final static float dot(Vec2D a, Vec2D b) {
		return a.x * b.x + a.y * b.y;
	}

	public final static float cross(Vec2D a, Vec2D b) {
		return a.x * b.y - a.y * b.x;
	}

	public final static Vec2D cross(Vec2D a, float s) {
		return new Vec2D(s * a.y, -s * a.x);
	}

	/* djm created */
	public final static void crossToOut(Vec2D a, float s, Vec2D out){
		float tempy = -s * a.x;
		out.x = s * a.y;
		out.y = tempy;
	}

	public final static Vec2D cross(float s, Vec2D a) {
		return new Vec2D(-s * a.y, s * a.x);
	}

	/* djm created */
	public final static void crossToOut(float s, Vec2D a, Vec2D out){
		float tempY = s * a.x;
		out.x = -s * a.y;
		out.y = tempY;
	}

	public final static void negateToOut(Vec2D a, Vec2D out){
		out.x = -a.x;
		out.y = -a.y;
	}

	public final static Vec2D min(Vec2D a, Vec2D b) {
		return new Vec2D(a.x < b.x ? a.x : b.x, a.y < b.y ? a.y : b.y);
	}

	public final static Vec2D max(Vec2D a, Vec2D b) {
		return new Vec2D(a.x > b.x ? a.x : b.x, a.y > b.y ? a.y : b.y);
	}

	/* djm created */
	public final static void minToOut(Vec2D a, Vec2D b, Vec2D out) {
		out.x = a.x < b.x ? a.x : b.x;
		out.y = a.y < b.y ? a.y : b.y;
	}

	/* djm created */
	public final static void maxToOut(Vec2D a, Vec2D b, Vec2D out) {
		out.x = a.x > b.x ? a.x : b.x;
		out.y = a.y > b.y ? a.y : b.y;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() { //automatically generated by Eclipse
		final int prime = 31;
		int result = 1;
		result = prime * result + Float.floatToIntBits(x);
		result = prime * result + Float.floatToIntBits(y);
		return result;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) { //automatically generated by Eclipse
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Vec2D other = (Vec2D) obj;
		if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x))
			return false;
		if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y))
			return false;
		return true;
	}
        public Vec2D toScreenUnits()
        {
            if(Main.noView)
                return new Vec2D(x * 8, y * 8);
            else
                return new Vec2D(x * Main.getView().scaleFactor, y * Main.getView().scaleFactor);
        }
        public Vec2D toWorldUnits()
        {
            if(Main.noView)
                return div(8);
            else
                return div(Main.getView().scaleFactor);
        }
    public Vec2D worldToScreen(MainView view)
    {
        float startWorldX = view.getCamPosition().x - Main.getView().width/view.scaleFactor/2;
        float startWorldY =  view.getCamPosition().y - Main.getView().height/view.scaleFactor/2;
        float worldDistX = (x - startWorldX) * view.scaleFactor;
        float worldDistY = (y - startWorldY) * view.scaleFactor;
        return new Vec2D(worldDistX, Main.getView().height - worldDistY);
    }
    public Vec2D screenToWorld()
    {
        Vec2D v = new Vec2D(x, Main.getView().height - y).toWorldUnits();
        Vec2D worldStart = Main.getView().getCamPosition().add(Main.getView().centerToCorner.toWorldUnits());
        return v.add(worldStart);
    }
    public Vec2D round()
    {
        return new Vec2D(Math.round(x), Math.round(y));
    }
    public Vec2D roundHalf()
    {
        return new Vec2D((float) (Math.floor(x) + 0.5), (float) (Math.floor(y) + 0.5));
    }
    public Vec2D rotate(float r)
    {
        float a = (float) (toAngle() + r);
        return new Vec2D(a).mul(length());
    }
}